/*  Pattern based natural language processing tools for Estonian.
    Copyright (C) 2013 University of Tartu

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "Rule.hpp"

#include <boost/unordered_set.hpp>
#include <boost/unordered_map.hpp>

namespace patnlp
{

std::map<Rule, OrderedDocCover> doc_basic_rule_covers(Document const& document, long const radius)
{
    boost::unordered_map<Rule, LongVector > map;
    long const N = static_cast<long>(document.size());
    Rule rule;
    // enumerate the rules
    for (long idx=0 ; idx<N ; ++idx) {
        for (long offset=-radius ; offset<=radius ; ++offset) {
            long pos = idx + offset;
            if (pos<0 || pos>=N) {
                continue;
            }
            rule.first = offset;
            for (auto i=document[idx].begin(); i!=document[idx].end() ; ++i) {
                rule.second = *i;
                map[rule].push_back(idx);
            }
        }
    }
    std::map<Rule, OrderedDocCover> rulemap;
    for (auto i=map.begin() ; i!=map.end() ; ++i) {
        rulemap[i->first] = OrderedDocCover(N, i->second);
    }
    return rulemap;
}

std::map<Rule, OrderedCover> basic_rule_covers(Corpus const& corpus, long const radius)
{
    std::map<Rule, OrderedCover> map;
    std::map<Rule, OrderedDocCover> doc_map;
    for (auto i=corpus.begin() ; i!=corpus.end() ; ++i) {
        std::string const& doc_name = i->first;
        Document const& doc = i->second;
        doc_map = doc_basic_rule_covers(doc, radius);
        for (auto j=doc_map.begin() ; j!=doc_map.end() ; ++j) {
            map[j->first].add_doc_cover(doc_name, j->second);
        }
    }
    return map;
}

std::set<Conjunction> doc_full_conjunctions(Document const& document, long const radius)
{
    std::set<Conjunction> conjunctions;
    Conjunction conjunction;
    long const N = static_cast<long>(document.size());
    Rule rule;
    // enumerate the rules
    for (long idx=0 ; idx<N ; ++idx) {
        conjunction.clear();
        for (long offset=-radius ; offset<=radius ; ++offset) {
            long pos = idx + offset;
            if (pos<0 || pos>=N) {
                continue;
            }
            rule.first = offset;
            for (auto i=document[idx].begin(); i!=document[idx].end() ; ++i) {
                rule.second = *i;
                conjunction.push_back(rule);
            }
        }
        // sort the rules in conjunction
        std::sort(conjunction.begin(), conjunction.end());
        conjunctions.insert(conjunction);
    }
    return conjunctions;
}

std::vector<Conjunction> full_conjunctions(Corpus const& corpus, long const radius)
{
    std::set<Conjunction> conjunctions;
    std::set<Conjunction> doc_conjunctions;
    for (auto i=corpus.begin() ; i!=corpus.end() ; ++i) {
        doc_conjunctions = doc_full_conjunctions(i->second, radius);
        conjunctions.insert(doc_conjunctions.begin(), doc_conjunctions.end());
    }
    return std::vector<Conjunction>(conjunctions.begin(), conjunctions.end());
}

std::map<Rule, long> doc_rule_count(Document const& document, long const radius)
{
    std::map<Rule, long> map;
    long const N = static_cast<long>(document.size());
    Rule rule;
    // enumerate the rules
    for (long idx=0 ; idx<N ; ++idx) {
        for (long offset=-radius ; offset<=radius ; ++offset) {
            long pos = idx + offset;
            if (pos<0 || pos>=N) {
                continue;
            }
            rule.first = offset;
            for (auto i=document[idx].begin(); i!=document[idx].end() ; ++i) {
                rule.second = *i;
                map[rule] += 1;
            }
        }
    }
    return map;
}

std::map<Rule, long> corpus_rule_count(Corpus const& corpus, long const radius)
{
    std::map<Rule, long> map;
    std::map<Rule, long> doc_map;
    for (auto i=corpus.begin() ; i!=corpus.end() ; ++i) {
        doc_map = doc_rule_count(i->second, radius);
        for (auto j=doc_map.begin() ; j!=doc_map.end() ; ++j) {
            map[j->first] += j->second;
        }
    }
    return map;
}

std::vector<Conjunction> conjunction_pairs(std::vector<Conjunction> const& A, std::vector<Conjunction> const& B)
{
    std::set<Conjunction> conjunctions;
    std::set<Rule> rules;
    Conjunction conjunction;
    for (auto i=A.begin() ; i!=A.end() ; ++i) {
        for (auto j=B.begin() ; j!=B.end() ; ++j) {
            rules.clear();
            conjunction.clear();
            std::copy(i->begin(), i->end(), std::inserter(rules, rules.end()));
            std::copy(j->begin(), j->end(), std::inserter(rules, rules.end()));
            std::copy(rules.begin(), rules.end(), std::back_inserter(conjunction));
            conjunctions.insert(conjunction);
        }
    }
    std::vector<Conjunction> result;
    result.reserve(conjunctions.size());
    result.insert(result.end(), conjunctions.begin(), conjunctions.end());
    return result;
}

std::vector<Conjunction> unique_conjunctions(std::vector<Conjunction> const& A)
{
    boost::unordered_set<Conjunction> conjunctions;
    std::copy(A.begin(), A.end(), std::inserter(conjunctions, conjunctions.end()));
    std::vector<Conjunction> result;
    result.reserve(conjunctions.size());
    std::copy(conjunctions.begin(), conjunctions.end(), std::back_inserter(result));
    return result;
}

OrderedCover conjunction_cover(Conjunction const& c, std::map<Rule, OrderedCover> const& basic_covers)
{
    assert (c.size() > 0);
    std::vector<OrderedCover> covers;
    covers.reserve(c.size());
    for (auto i=c.begin() ; i!=c.end() ; ++i) {
        auto j = basic_covers.find(*i);
        if (j == basic_covers.end()) { // return empty cover
            OrderedCover cov;
            return cov;
        }
        covers.push_back(j->second);
    }
    OrderedCover cov = covers[0];
    for (unsigned int i=1 ; i<covers.size() ; ++i) {
        cov &= covers[i];
    }
    return cov;
}

std::vector<Conjunction>
reorder_recall(std::vector<Conjunction> const& conjunctions,
               std::map<Rule, OrderedCover> const& basic_covers,
               OrderedCover const& trueCover)
{
    std::vector<std::pair<double, long> > buffer;
    buffer.reserve(conjunctions.size());
    // compute the recalls
    for (size_t idx=0 ; idx<conjunctions.size() ; ++idx) {
        OrderedCover c = conjunction_cover(conjunctions[idx], basic_covers);
        auto metrics = c.metrics(trueCover);
        buffer.push_back( {metrics.recall(), idx});
    }
    // sort according to recall decreasingly
    std::sort(buffer.begin(), buffer.end(), std::greater<std::pair<double, long> >());
    std::vector<Conjunction> result;
    // copy and return conjunctions
    result.reserve(buffer.size());
    for (auto i=buffer.begin() ; i!=buffer.end() ; ++i) {
        result.push_back(conjunctions[i->second]);
    }
    return result;
}

} // namespace patnlp
